
use std::io::Stdout;
use std::io::Write;
use crate::error::*;

use termion::raw::{IntoRawMode, RawTerminal};
use termion::event::Key;
use termion::input::TermRead;

pub struct Console {
    stdout: RawTerminal<Stdout>,
    history_inputs: Vec<String>,
    exit: bool,
}

impl Console {
    pub fn new() -> Self {
        let mut stdout = std::io::stdout().into_raw_mode().unwrap();
        write!(stdout, "{}", termion::clear::All).unwrap();
        write!(stdout, "{}", termion::cursor::Goto(1, 1)).unwrap();
        stdout.flush().unwrap();

        Self {
            stdout,
            history_inputs: Vec::new(),
            exit: false,
        }
    }

    pub fn exit(&self) -> bool {
        self.exit
    }

    pub fn print<T: std::fmt::Display + ?Sized>(&mut self, value: &T) -> Result<()> {
        write!(self.stdout, "{}", value)?;
        self.stdout.flush()?;
        Ok(())
    }

    pub fn println<T: std::fmt::Display + ?Sized>(&mut self, value: &T) -> Result<()> {
        write!(self.stdout, "{}\n\r", value)?;
        self.stdout.flush()?;
        Ok(())
    }

    pub fn read_line(&mut self) -> Result<String> {
        self.print("\r$ ")?;

        let mut input = String::new();
        let stdin = std::io::stdin();
        let mut cur_pos: usize = 0;
        let mut history_idx: usize = 0;

        for key in stdin.keys() { 
            let key: Key = key?;
            match key {
                Key::Backspace => {
                    if cur_pos > 0 {
                        cur_pos -= 1;
                        input.remove(cur_pos);

                        self.cursor_left_no_flush(cur_pos + 1)?;
                        self.print_space_no_flush(input.len() + 1)?;
                        self.cursor_left_no_flush(input.len() + 1)?;
                        self.print_no_flush(&input)?;
                        self.cursor_left_no_flush(input.len())?;
                        self.cursor_right_no_flush(cur_pos)?;
                        self.stdout.flush()?;
                    }
                }
                Key::Left => {
                    if cur_pos > 0 {
                        self.cursor_left(1)?;
                        cur_pos -= 1;
                    }
                }
                Key::Right => {
                    if cur_pos < input.len() {
                        self.cursor_right(1)?;
                        cur_pos += 1;
                    }
                }
                Key::Up => {
                    if let Some((idx, history_input)) = self.history_inputs.iter()
                        .rev()
                        .enumerate()
                        .skip(history_idx)
                        .nth(0) 
                    {
                        input = history_input.clone();
                        history_idx = idx + 1;
                    }

                    self.cursor_left_no_flush(cur_pos)?;
                    self.print(&input)?;
                    cur_pos = input.len();
                }
                Key::Down => {

                }
                Key::Home => {

                }
                Key::PageUp => {

                }
                Key::PageDown => {

                }
                Key::BackTab => {

                }
                Key::Delete => {

                }
                Key::Insert => {

                }
                Key::F(_) => {
                    
                }
                Key::Char(ch) => {
                    if ch == '\n' {
                        self.print("\n\r")?;
                        self.history_inputs.push(input.clone());
                        break;
                    } else {
                        input.insert(cur_pos, ch);
                        cur_pos += 1;
                        self.cursor_left_no_flush(cur_pos - 1)?;
                        self.print_space_no_flush(input.len() - 1)?;
                        self.cursor_left_no_flush(input.len() - 1)?;
                        self.print_no_flush(&input)?;
                        self.cursor_left_no_flush(input.len())?;
                        self.cursor_right_no_flush(cur_pos)?;
                        self.stdout.flush()?;
                    }
                }
                Key::Alt(_) => {
                
                }
                Key::Ctrl(ch) => {
                    match ch {
                        'c' => self.exit = true,
                        'C' => self.exit = true,
                        _ => {}
                    }
                }
                Key::Null => {

                }
                Key::Esc => {

                }
                _ => {}
            }
        }

        Ok(input)
    }
}

impl Console {
    fn cursor_left(&mut self, dis: usize) -> Result<()> {
        self.cursor_left_no_flush(dis)?;
        self.stdout.flush()?;
        Ok(())
    }

    fn cursor_right(&mut self, dis: usize) -> Result<()> {
        self.cursor_right_no_flush(dis)?;
        self.stdout.flush()?;
        Ok(())
    }

    #[allow(unused)]
    fn print_space(&mut self, size: usize) -> Result<()> {
        self.print_space_no_flush(size)?;
        self.stdout.flush()?;
        Ok(())
    }

    pub fn print_no_flush<T: std::fmt::Display + ?Sized>(&mut self, value: &T) -> Result<()> {
        write!(self.stdout, "{}", value)?;
        Ok(())
    }

    fn cursor_left_no_flush(&mut self, dis: usize) -> Result<()> {
        if dis != 0 {
            write!(self.stdout, "\x1B[{}D", dis)?;
        }
        Ok(())
    }

    fn cursor_right_no_flush(&mut self, dis: usize) -> Result<()> {
        if dis != 0 {
            write!(self.stdout, "\x1B[{}C", dis)?;
        }
        Ok(())
    }

    fn print_space_no_flush(&mut self, size: usize) -> Result<()> {
        self.stdout.write_all(&vec![0x20; size])?;
        Ok(())
    }
}